﻿Module Primary

    ''' <summary>
    ''' Used to enable or disable Log Timestamps by default
    ''' </summary>
    ''' <remarks></remarks>
    Public Const EnableTimeStampsByDefault As Boolean = True

    ''' <summary>
    ''' Enumerates all possible platforms where the application can run
    ''' </summary>
    ''' <remarks>MacOS and Linux platforms are supported with Mono</remarks>
    Public Enum PlatformName
        Windows
        MacOS
        Linux
        Unsupported
    End Enum

    ''' <summary>
    ''' Stores the currently detected Platform where the compiler is running
    ''' </summary>
    ''' <remarks></remarks>
    Public Platform As PlatformName

    ''' <summary>
    ''' Adds text to the Console Window
    ''' </summary>
    ''' <param name="text">Text to be added</param>
    ''' <param name="leaveOpen">If true, the caret will not be moved to the next line automatically after writting to the Console</param>
    ''' <param name="timestamp">If true, a Timestamp will be added to the beggining of the newly inserted line</param>
    ''' <remarks></remarks>
    Public Sub AddLog(ByVal text As String, ByVal leaveOpen As Boolean, ByVal timestamp As Boolean)
        ' Primary AddLog method, All the other ones will end calling this one
        Dim prefix As String = ""
        If timestamp = True Then
            Dim stamp As Date = Now
            Dim component As String
            component = stamp.Hour
            If component.Length = 1 Then component = "0" & component
            prefix = "[" & component & ":"
            component = stamp.Minute
            If component.Length = 1 Then component = "0" & component
            prefix &= component & "] "
        End If

        If (prefix.Length + text.Length) > (Console.BufferWidth - Console.CursorLeft) Then
            ' Text too big
            text = Left(text, Console.BufferWidth - Console.CursorLeft - 3 - prefix.Length)
            text &= "-/>"
            leaveOpen = True
        End If

        If leaveOpen = True Then
            Console.Write(prefix & text)
        Else
            Console.WriteLine(prefix & text)
        End If
    End Sub
    Public Sub AddLog(ByVal text As String, ByVal leaveOpen As Boolean)
        AddLog(text, leaveOpen, EnableTimeStampsByDefault)
    End Sub
    Public Sub AddLog(ByVal text As String)
        AddLog(text, False, EnableTimeStampsByDefault)
    End Sub
    Public Sub AddLog()
        AddLog("", False, False)
    End Sub
    ''' <summary>
    ''' Adds text to the Console Window that will be shown only if the Verbose flag is specified
    ''' </summary>
    ''' <param name="text"></param>
    ''' <remarks></remarks>
    Public Sub AddLogV(ByVal text As String)
        If Configuration.CurrentConfiguration.Verbose = True Then AddLog(text)
    End Sub

    ''' <summary>
    ''' Used to clear the Console output
    ''' </summary>
    ''' <param name="lastLine">If true, only the last line will be cleared</param>
    ''' <remarks></remarks>
    Public Sub ClearLog(ByVal lastLine As Boolean)
        If (Not lastLine) Then
            Console.Clear()
            Exit Sub
        End If

        If Console.CursorLeft = 0 Then
            ' At the start of the current line. Move upwards before deleting
            If Not Console.CursorTop = 0 Then Console.CursorTop -= 1
        End If

        ' Position the caret at the start of the current line and delete everything
        Console.CursorLeft = 0
        Dim currLine As Integer = Console.CursorTop
        Dim clearLine As String = StrDup(Console.BufferWidth, " ")
        Console.Write(clearLine)

        ' Reset caret position
        Console.CursorLeft = 0
        Console.CursorTop = currLine
    End Sub
    Public Sub ClearLog()
        ClearLog(False)
    End Sub

    ''' <summary>
    ''' Used to overwrite the last line on the Console window
    ''' </summary>
    ''' <param name="text">Text that will be used to overwrite the last line</param>
    ''' <param name="leaveOpen">If true, the caret will not be moved to the next line automatically after writting to the Console</param>
    ''' <remarks></remarks>
    Public Sub Rewrite(ByVal text As String, ByVal leaveOpen As Boolean, ByVal timestamp As Boolean)
        ClearLog(True)
        AddLog(text, leaveOpen, timestamp)
    End Sub


    'Public Sub Rewrite(ByVal text As String, ByVal leaveOpen As Boolean)
    '    Rewrite(text, leaveOpen, EnableTimeStampsByDefault)
    'End Sub
    Public Sub Rewrite(ByVal text As String)
        Rewrite(text, False, EnableTimeStampsByDefault)
    End Sub

    Function Main() As Integer
        ' Pre-check for the modifier "/help_exitcode=X"
        MainCheckForExitCodeQuery()

        Dim outWarning As String = ""

        ' Detect Platform
        Select Case Environment.OSVersion.Platform
            Case PlatformID.Unix, 128
                Platform = PlatformName.Linux
                outWarning = "Detected UNIX/LINUX Environment"
            Case PlatformID.MacOSX
                Platform = PlatformName.MacOS
                outWarning = "WARNING: Detected Mac OS X Environment. Partially supported."
            Case PlatformID.Win32NT, PlatformID.Win32S, PlatformID.Win32Windows, PlatformID.WinCE
                Platform = PlatformName.Windows
            Case Else
                Platform = PlatformName.Unsupported
                outWarning = "ERROR: Cant realiable identify current platform. Unsupported."
        End Select

        ' Application Init
        AddLog()
        AddLog("ACU", False, False)
        AddLog("Arphoon Package Compilation Utility", False, False)
        AddLog("By KDERazorback", False, False)
        AddLog("============================================================", False, False)
        AddLog()
        If Not outWarning = "" Then
            AddLog(outWarning, False, False)
            Threading.Thread.Sleep(2000)
        End If
        AddLog("   Utility Version: " & My.Application.Info.Version.ToString, False, False)
        AddLog("   Captain's Journal: " & Now.ToString(System.Globalization.CultureInfo.InvariantCulture), False, False)
        AddLog()
        AddLog()

        Configuration.InitializeDefaultConfiguration()
        Bootstrapping.ProcessCommandLineParameters()

        If Configuration.CurrentConfiguration.PrintHelpOnly = True Then
            PrintHelp(True)
            Return SupportedExitCodes.NoTaskSpecified
        End If

        If Configuration.CurrentConfiguration.JumpToShell = True Then
            'Dim shell As New InteractiveShell
            InteractiveShell.ShellMain()
            AddLog("< press any key to exit >", False, False)
            Console.ReadKey()
            Return SupportedExitCodes.ShellDrop
        End If

        ' Autocompile
        Try
            AddLog("AUTOCOMPILATION MODE")
            LastErrorCode = AutoCompiler()
        Catch ex As Exception
            AddLog()
            AddLog("CRITICAL: Unhandled exception found when autocompiling")
            AddLogV("     ex: " & ex.Message)
            LastErrorCode = SupportedExitCodes.CompilerException
        End Try

        ' Set output code
        AddLogV("")
        AddLogV("Compilation ended with code <" & UCase(LastErrorCode.ToString) & ":" & Integer.Parse(LastErrorCode, System.Globalization.CultureInfo.InvariantCulture) & ">")
        Return LastErrorCode
    End Function

    Public Function AutoCompiler() As SupportedExitCodes
        ' Check pre-requisites
        AddLog("Checking compilation parameters...")
        AddLog()
        If (CurrentConfiguration.InputFilename Is Nothing) OrElse (Configuration.CurrentConfiguration.InputFilename.Count < 1) Then
            AddLog("InputFiles  : FAILED!")
            Return SupportedExitCodes.NoSourceFiles
        Else
            AddLog("InputFiles  : OK")
        End If
        If (CurrentConfiguration.OutputFilename.Length < 1) Then
            AddLog("OutputFile  : FAILED!")
            Return SupportedExitCodes.NoTargetFile
        Else
            AddLog("OutputFile  : OK")
        End If

        ' Check Compilation protocol
        Dim xcompiler As Compilers.GenericCompiler = Nothing
        AddLogV("Getting compiler for protocol <" & CurrentConfiguration.OutputProtocol & ">...")

        Dim i As Integer
        For i = 0 To Configuration.AvailableCompilers.Length - 1
            If UCase(CurrentConfiguration.OutputProtocol) = UCase(Configuration.AvailableCompilers(i).CompilerProtocol) Then
                AddLogV("Compiler found for specified protocol.")
                AddLogV(Configuration.AvailableCompilers(i).CompilerName)
                xcompiler = Configuration.AvailableCompilers(i)
                AddLog("Protocol    : OK")
                Exit For
            End If
        Next
        If xcompiler Is Nothing Then
            AddLog("Protocol    : FAILED!")
            AddLog()
            AddLog("ERROR!: No compiler found for defined protocol.")
            AddLog("Remember that a protocol follows the syntax of 'M.m.b.r_x'")
            Return SupportedExitCodes.NoCompilerFound
        End If

        AddLog()
        AddLog("Starting compilation simulation...")
        Dim simResult As Compilers.PackageCompilerSimulationResult
        simResult = xcompiler.Simulate()

        If Not simResult.Code = Compilers.PackageCompilationResultCode.CompilationSucceeded Then
            AddLog("ERROR!: Simulation step failed.")
            AddLog("  Code: " & simResult.Code.ToString)
            AddLog("   Msg: " & simResult.Message)

            Return simResult.Code
        End If

        AddLog("Simulation succeeded.")

        ' Compile
        AddLog()
        AddLog("====================================")
        AddLog("    Starting real compilation...")
        AddLog("====================================")
        Dim compResult As Compilers.PackageCompilationResult
        compResult = xcompiler.Compile()

        If Not compResult.Code = Compilers.PackageCompilationResultCode.CompilationSucceeded Then
            AddLog("ERROR!: Compilation step failed.")
            AddLog("  Code: " & compResult.Code.ToString)
            AddLog("   Msg: " & compResult.Message)

            Return compResult.Code
        End If

        AddLog()
        AddLog("Compilation complete!")
        AddLog(compResult.Statistics.PackedFiles & " files packed")
        AddLog("Output size: " & Compilers.Commons.FormatSizeAsHumanReadable(compResult.Statistics.OutputSize) & " @ " & Compilers.Commons.FormatSizeAsHumanReadable(compResult.Statistics.PackedBytes))
        AddLog("Started at: " & compResult.Statistics.StartTime)
        AddLog("Ended at: " & compResult.Statistics.EndTime)
        AddLog("Entire process took: " & compResult.Statistics.TcsDiffTime)
        AddLog("Buffer Avg: " & Compilers.Commons.FormatSizeAsHumanReadable(compResult.Statistics.AverageBufferUsed) & "  Max: " & Compilers.Commons.FormatSizeAsHumanReadable(compResult.Statistics.MaxBufferUsed) & "  Min: " & Compilers.Commons.FormatSizeAsHumanReadable(compResult.Statistics.MinBufferUsed))
        AddLog("Ticks Avg: " & Compilers.Commons.GetFormattedTime(New TimeSpan(compResult.Statistics.AverageTicksPerIO)) & "  Max: " & Compilers.Commons.GetFormattedTime(New TimeSpan(compResult.Statistics.MaxTicksPerIO)) & "  Min: " & Compilers.Commons.GetFormattedTime(New TimeSpan(compResult.Statistics.MinTicksPerIO)))
        AddLogV("Tick barrier: " & compResult.Statistics.TickBarrier)
        AddLogV("Tick length: " & compResult.Statistics.TickLength)

        Return SupportedExitCodes.CompilationSucceeded
    End Function

    Public Sub MainCheckForExitCodeQuery()
        ' Check if there is a command line parameter in the form of "/help_exitcode=X"
        ' Those commands are designed to lookup in the Application Database and return the help for an specified error code defined by "X"

        If My.Application.CommandLineArgs Is Nothing Then Exit Sub
        If Not My.Application.CommandLineArgs.Count = 1 Then Exit Sub

        Try
            Dim modifier As String = My.Application.CommandLineArgs.Item(0)
            modifier = LCase(modifier)
            modifier = Trim(modifier)
            If modifier Like "/help_exitcode=*" Then
                ' Help Query
                Dim tmp() As String = Split(modifier, "=")
                If tmp Is Nothing Then Exit Sub
                If Not tmp.Length = 2 Then Exit Sub
                Dim output As String = ParseExitCode(tmp(1))
                Console.WriteLine(output)
                End
            Else
                ' Not a help Query
                Exit Sub
            End If
        Catch ex As Exception
            Exit Sub
        End Try
    End Sub

    ''' <summary>
    ''' Consolidates an splitted instruction to its string delimited variant at the specific indexes
    ''' </summary>
    ''' <param name="splittedInstruction">Array that stores the splitted instruction</param>
    ''' <param name="startIndex">Index from where to start creating the new string</param>
    ''' <param name="endIndex">Last index that will be included in the string</param>
    ''' <param name="separator">Delimiter string used to separate items on the output</param>
    ''' <returns>The consolidated and string delimited version of the specified instruction array</returns>
    ''' <remarks>This function converts this splitted instruction array {"a","b","c","d"} to "a$b$c$d" when using "$" as delimiter</remarks>
    Public Function ConsolidateParameters(ByVal splittedInstruction() As String, ByVal startIndex As Integer, ByVal endIndex As Integer, ByVal separator As String) As String
        If splittedInstruction Is Nothing Then Return Nothing
        If splittedInstruction.Length = 0 Then Return Nothing

        If startIndex < 0 Then startIndex = 0
        If endIndex >= splittedInstruction.Length Then endIndex = splittedInstruction.Length - 1

        Dim output As String = ""
        Dim i As Integer
        For i = startIndex To endIndex
            output &= splittedInstruction(i)
            If Not i = endIndex Then output &= separator
        Next

        Return output
    End Function

    ''' <summary>
    ''' Parses an exit code and returns its associated string value
    ''' </summary>
    ''' <param name="exitCode">Exit Code that will be parsed</param>
    ''' <returns>An String value that represents the error description for the specified exit code</returns>
    ''' <remarks></remarks>
    Public Function ParseExitCode(ByVal exitCode As Integer) As String
        Select Case exitCode
            Case 0
                Return "No Error"
                ' ////////////////////////////////////////////////// 1 To 64
            Case 1
                Return "Not enough parameters to skip Interactive Shell"
            Case 2
                Return "No Source Files Specified"
            Case 3
                Return "No Output File Specified"
            Case 4
                Return "No Protocol Specified"
            Case 5
                Return "Protocol Not Supported"
                ' ////////////////////////////////////////////////// 65 To 89
            Case 65
                Return "Cant Open Output File"
            Case 66
                Return "Insufficient Disk Space"
            Case 67
                Return "Compilation Exceeds Compiler Limitations"
                ' ////////////////////////////////////////////////// 90 To 127
            Case 90 To 127
                Return "Reserved"
                ' ////////////////////////////////////////////////// 128 To 4224
            Case 128 To 4224
                Dim fileID As Integer = exitCode - 128
                Return "SIM: Invalid Source File with ID <" & fileID & ">"
                ' ////////////////////////////////////////////////// 4225 To 4321
            Case 4225 To 8321
                Dim fileID As Integer = exitCode - 4225
                Return "COMP: Invalid Source File with ID <" & fileID & ">"
            Case Else
                Return "Invalid Exit Code"
        End Select
    End Function
End Module
